#!/usr/bin/python

import sys,getopt
import commands
import calendar
import operator
import time
import os
import re
import JoDef
import JoTime
import xml.dom.minidom
from datetime import *
from dateutil.parser import *
from twisted.python import log
from os import path

import pdb

#utils
numre  = re.compile("^\+?\-?\d+\.?\d* *")
datere = re.compile("\d\d?\/\d\d?|\d\d?:\d\d?|\d\d?[ampAMP]*$")
subre  = re.compile("_[A-Z0-9]*_")
subsre = re.compile("_([A-Z0-9]*)_*")

def is_num(input):
	if numre.search(input):
		return True
	return False

def is_date(input):
    try:
        parse(input,fuzzy=True)
        return True
    except ValueError:
        return False


def has_subvar(textnode):
	if subre.search(textnode):
		return True
	return False

class SequenceNext(Exception):
	def __str__(self):
		return "SequenceNext"

class SequenceEnd(Exception):
	def __str__(self):
		return "SequenceEnd"

class InputNotValid(Exception):
	def __str__(self):
		return "InputNotValid"

class query:
	"""State class for journal commands and interviews"""
	state   = "command"
	prompt  = "enter command, h for help"
	next    = "command"
	noinput = "false"
	index   = 0
	type    = "str"
	repeat  = False
	sequence= 0
	runpass = 0

	def __init__(self,seq,interview,state,noin,valid,redo,type,repeat,value):
		self.sequence = seq
		self.interview = interview
		self.state = state
		self.noinput  = noin
		self.valid_expr = valid
		self.redo  = redo
		self.type  = type
		self.gotos = []  # (next,eval)
		self.value = value
		if type.lower() == 'list':
			self.value = []
		if repeat.lower() == 'true':
			self.repeat = True

	def get_state(self):
		return self.state

	def get_name(self):
		return self.state

	def valid(self,input):
		def valid(state):
			return self.valid(self.interview[state])
		try:
			if not self.valid_expr or eval(self.valid_expr):
				return True
			else:
				return False
		except:
			return False
	
	def set_index(self,index):
		self.index = index

	def get_index(self):
		return self.index

	def set_prompt(self,prompt):
		self.prompt = prompt

	def get_prompt(self):
		prompt = self.prompt
		if has_subvar(prompt):
			allvars = subsre.findall(prompt)	
			for var in allvars:
				if self.interview.has_val(var.lower()):
					prompt = subre.sub(self.interview[var.lower()],prompt)
				else:
					prompt = subre.sub("",prompt)
					prompt = re.sub("  "," ",prompt)
		return prompt

	def add_goto(self,gotonext, gotoeval):
		self.gotos.append((gotonext,gotoeval))

	def get_next(self):
		def has_val(name):
			return self.interview.has_val(name)
		for g in self.gotos:
			input = self.value
			if not g[1] or eval(g[1]):
				return g[0]
		return self.interview.sequence_next(self)

	def get_sequence_next(self):
		return self.interview.sequence_next(self)
		
	def get_last(self):
		return self.interview.sequence_last()

	def process_state(self,input):
		self.runpass += 1
		if input == '.':
			return self.get_next()
		if input == '..':
			return None
		if self.valid(input):
			self.set_value(input)
			if self.repeat:
				return self.state
			return self.get_next()
		else:
			set_prompt(self.redo)
			return self.state
		
	def run(self):
		def has_val(name):
			return self.interview.has_val(name)
		tries = 1
		prompt = self.get_prompt(tries)
		input = self.get_value()
		while not self.valid_expr or not self.valid(input):
			tries += 1
			sys.stdout.write("%s" % prompt)
			if self.noinput.lower() == "true":
				raise SequenceNext
			input = sys.stdin.readline().strip()
			if input == '.':
				input = '';
				return self.get_next()
			if input == '..':
				return self.get_last()
			self.set_value(input)
			if self.repeat:
				return self.state
		return self.get_next()
		
	def set_value(self,value):
		if self.type == 'list':
			try:
				self.value.append(value)
			except KeyError:
				self.value = []
				if value:
					self.value.append(value)
		else:	
			self.value = value

	def get_value(self):
		return self.value	

	def sprint(self):
		print self.get_prompt()
		print self.state, self.value, self.next, self.nexteval, self.redo
		for g in self.gotos:
			print g

class interview:
	"""State machine class for journal commands and interviews"""
	args     = {}
	names    = []
	states   = {}
	defaults = {}
	results  = {}
	index = 0

	def __init__(self,command,*args,**kwargs):
		self.active_state = None
		self.args = kwargs
		if not command: 
			return
		fpath = os.getenv('JOIVPATH',JoDef.JOIVDIR)	
		xmlfile = path.join(fpath,command+".xml")
		if path.exists(xmlfile):
			doc = xml.dom.minidom.parse(xmlfile)
			self.handleStates(doc.getElementsByTagName("state"))
		else:
			log.msg("No read access on "+xmlfile)	

	def __getitem__(self,name):
		return self.states[name].get_value()

	def has_val(self,name):
		if self.states[name].get_value():
			return True
		else:
			return False
			
	def getText(self,nodelist):
		rc = ""
		for node in nodelist:
			if node.nodeType == node.TEXT_NODE:
				rc = rc + node.data
		return rc

	def handleStates(self,states):
		seq = 1
		for state in states:
			self.handleState(state,seq)
			seq += 1

	def handleState(self,state,seq):
		sname  =  state.getAttribute('name')
		svalid =  state.getAttribute('valid')
		sredo  =  state.getAttribute('redo')
		snoin  =  state.getAttribute('noinput')
		stype  =  state.getAttribute('type')
		srepeat  =  state.getAttribute('repeat')
		sval = None
		try:
			sval = self.args[sname]
		except:
			pass
		newst  = query(seq,self,sname,snoin,svalid,sredo,stype,srepeat,sval)
		self.handlePrompts(newst,state.getElementsByTagName("prompt"))
		self.handleGotos(newst,state.getElementsByTagName("goto"))
		self.add_state(sname,newst)

	def handlePrompts(self,state,prompts):
		for p in prompts:
			self.handlePrompt(state,p)

	def handlePrompt(self,state,prompt):
		self.handlePromptText(state,prompt)

	def handlePromptText(self,state,prompt):
		state.set_prompt(self.getText(prompt.childNodes))

	def handleGotos(self,state,gotos):
		for g in gotos:
			self.handleGoto(state,g)

	def handleGoto(self,state,goto):
		gotoeval = goto.getAttribute('eval')
		gotonext = goto.getAttribute('next')
		state.add_goto(gotonext,gotoeval)

	def add_state(self,name,st):
		st.set_index(len(self.names))
		self.states[name] = st
		self.names.append(name)
	
	def sequence_next(self,st):
		if not st:
			return self.names[0]
		else:
			try:
				return self.names[st.get_index()+1]
			except IndexError:
				return None

	def sequence_last(self):
		return self.names[len(self.names)-1]

	def valid(self):
		nextstate = self.sequence_next(None)
		while nextstate and nextstate in self.states:
			state = self.states[nextstate]
			if not state.valid(state.value):
				return False
			nextstate = state.get_next()
		return True

	def prompt(self):
		if not self.active_state:
			state_name = self.sequence_next(None)
			if state_name in self.states:
				self.active_state = self.states[state_name]
			else:
				return None
		prompt = self.active_state.get_prompt()
		while self.active_state and self.active_state.noinput.lower() == 'true':
			nextstate = self.active_state.get_next()
			if nextstate and nextstate in self.states:
				self.active_state = self.states[nextstate]
				prompt += self.active_state.get_prompt()
			else:
				self.active_state = None
		return prompt

	def process(self,input):
		if self.active_state:
			nextstate = self.active_state.process_state(input)
			if self.active_state.value:
				self.results[self.active_state.state] = self.active_state.value
			if nextstate in self.states:
				self.active_state = self.states[nextstate]
				return self.active_state.sequence
		return None
		
	def get_results(self):
		return self.results

	def run(self):
		nextstate = self.sequence_next(None)
		while nextstate:
			try:
				state = self.states[nextstate]
				nextstate = state.run()
			except KeyError:
				nextstate = self.sequence_next(state) 
			except SequenceNext:
				nextstate = self.sequence_next(state) 
			except SequenceEnd:
				nextstate = None

